package model.simulationmodel;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;

import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.LogicEditorElement;
import model.editormodel.LogicSelfdefinedEditorElement;
import model.editormodel.Output;
import model.simulationmodel.logicelements.Logic2MX;
import model.simulationmodel.logicelements.LogicAND;
import model.simulationmodel.logicelements.LogicCLOCK;
import model.simulationmodel.logicelements.LogicFLIPFLOP;
import model.simulationmodel.logicelements.LogicLamp;
import model.simulationmodel.logicelements.LogicNAND;
import model.simulationmodel.logicelements.LogicNODE;
import model.simulationmodel.logicelements.LogicNOT;
import model.simulationmodel.logicelements.LogicOR;
import model.simulationmodel.logicelements.LogicSELFDEFINED;
import model.simulationmodel.logicelements.LogicSimulationElement;
import model.simulationmodel.logicelements.LogicSwitch;
import model.simulationmodel.logicelements.LogicXOR;

/**
 * <b>Erbt:</b> <code>Observable</code> <br>
 * <b>In Verbindung stehende Klassen:</b> <code>EditorModel, ClockHandler,
 * LogicSimulationElement </code><br>
 * <b>Zugehoerige Observer:</b> <code>SimulationArea</code><br>
 * <b>Funktion:</b> Verwaltung der Simulation und der daran beteiligten Bauteile<br>
 * <br>
 * Dieses Model verwaltet die aktuell vorhandenen Simulationsobjekte. Diese
 * Klasse wird von der Klasse <code>SimulationArea</code> ueberwacht. Ausserdem
 * besitzt es eine Referenz auf das <code>EditorModel</code>. Hat Methoden zum
 * Starten, Pausieren und Stoppen der Simulation und zum Propagieren der
 * logischen Werte.
 * 
 * @author Team Locsi
 * 
 */
public class SimulationModel extends Observable {

	private SimulationThread simThread;
	private EditorModel eModel;
	private ClockHandler cHandler;
	private Map<String, LogicSimulationElement> sElementsToPaint;
	private Map<LogicEditorElement, LogicSimulationElement> sElements;
	private Map<String, LogicSimulationElement> clocksAndSwitches;
	private Map<String, LogicSimulationElement> lamps;
	private List<LogicSimulationElement> elementsToCalc = new ArrayList<LogicSimulationElement>();

	private boolean interruptedTruthTableCalculation;
	private boolean simulationIsVirgin;
	private SIMSTATE actState;

	private enum SIMSTATE {
		PLAY, PAUSE, STOP
	}

	/**
	 * Dieser Konstruktor erzeugt ein neues <code>SimulationModel</code>-Objekt.
	 * Dabei erhaelt es alle noetigen Informationen vom <code>EditorModel</code>
	 * .
	 * 
	 * @param eModel
	 *            <code>EditorModel</code> - Referenz auf das
	 *            <code>EditorModel</code>
	 */
	public SimulationModel(EditorModel eModel) {
		sElementsToPaint = new HashMap<String, LogicSimulationElement>();
		sElements = new HashMap<LogicEditorElement, LogicSimulationElement>();
		clocksAndSwitches = new HashMap<String, LogicSimulationElement>();
		lamps = new HashMap<String, LogicSimulationElement>();
		actState = SIMSTATE.STOP;
		this.eModel = eModel;
		this.cHandler = new ClockHandler(this);
	}

	/**
	 * Diese Methode wird beim Wechsel in den Simulationsmodus aufgerufen. Hier
	 * werden alle Attribute aktualisiert, also dem <code>EditorModel</code>
	 * angepasst.
	 */
	public void updateSimulationElements() {

		simulationIsVirgin = true;

		// zuerst alle Element loeschen
		sElementsToPaint.clear();
		sElements.clear();
		clocksAndSwitches.clear();
		lamps.clear();

		// Map mit allen Bauteilen aus cModel holen
		Map<String, LogicEditorElement> editorElements = getLogicElements();

		// zu jedem LogicEditorElement muss zugehoeriges LogicSimulationElement
		// erzeugt werden
		generateSimElementsRecursiv(editorElements, true);

		this.setChanged();
		this.notifyObservers(new SimulationMessage(
				SimulationMessage.SimulationMessageType.UPDATED_ELEMENTS,
				Collections.unmodifiableMap(sElementsToPaint)));
	}

	/**
	 * Diese Methode erzeugt alle <code>LogicSimulationElement</code>
	 * entsprechend zu der uebergebenen Map von <code>LogicEditorElement</code>
	 * 
	 * @param cModel
	 *            <code>Map</code><<code>LogicEditorElement</code>>
	 * @param isAddedToSelements
	 *            <code>boolean</code> - <code>true</code>, wenn Elemente zu
	 *            <code>sElementsToPaint</code> hinzugefuegt werden sollen,
	 *            <code>false</code>, sonst (also fuer Bauteile die sich in
	 *            einem selbstdefinierten Bauteil befinden
	 */
	private void generateSimElementsRecursiv(
			Map<String, LogicEditorElement> cModel, boolean isAddedToSelements) {

		// Map durchlaufen und das zugehoerige simElement erzeugen
		for (LogicEditorElement currentElement : cModel.values()) {

			// je nach Typ des aktuellen Elements wird das entsprechende
			// LogicSimulationElement erzeugt und zu den entsprechenden Listen:
			// sElements, sElementsToPaint ... hinzugefuegt
			switch (currentElement.getType()) {
			case AND:
				LogicAND and = new LogicAND(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), and);
				}
				sElements.put(currentElement, and);
				break;
			case OR:
				LogicOR or = new LogicOR(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), or);
				}
				sElements.put(currentElement, or);
				break;
			case NOT:
				LogicNOT not = new LogicNOT(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), not);
				}
				sElements.put(currentElement, not);
				break;
			case XOR:
				LogicXOR xor = new LogicXOR(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), xor);
				}
				sElements.put(currentElement, xor);
				break;
			case NAND:
				LogicNAND nand = new LogicNAND(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), nand);
				}
				sElements.put(currentElement, nand);
				break;
			case TWO_MX:
				Logic2MX mx = new Logic2MX(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), mx);
				}
				sElements.put(currentElement, mx);
				break;
			case FLIPFLOP:
				LogicFLIPFLOP ff = new LogicFLIPFLOP(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), ff);
				}
				sElements.put(currentElement, ff);
				break;
			case NODE:
				LogicNODE node = new LogicNODE(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), node);
				}
				sElements.put(currentElement, node);
				break;
			case CLOCK:
				LogicCLOCK clock = new LogicCLOCK(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), clock);
					clocksAndSwitches.put(currentElement.getName(), clock);
				}
				sElements.put(currentElement, clock);
				break;
			case LAMP:
				LogicLamp lamp = new LogicLamp(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), lamp);
					lamps.put(currentElement.getName(), lamp);
				}
				sElements.put(currentElement, lamp);
				break;
			case SWITCH:
				LogicSwitch swit = new LogicSwitch(this, currentElement);
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), swit);
					clocksAndSwitches.put(currentElement.getName(), swit);
				}
				sElements.put(currentElement, swit);
				break;

			// bei einem selbstdefinierten Bauteil wird ausserdem diese Methode
			// rekursiv aufgerufen, jedoch mit "false" als 2.Parameter, da diese
			// Elemente (also die Bestandteile eines selbstdefinierten Bauteils)
			// nicht in "sElementsToPaint" aufgenommen werden sollen
			case SELFDEFINED:
				LogicSELFDEFINED lsEE = new LogicSELFDEFINED(this,
						((LogicSelfdefinedEditorElement) currentElement));
				if (isAddedToSelements) {
					sElementsToPaint.put(currentElement.getName(), lsEE);
				}
				generateSimElementsRecursiv(
						((LogicSelfdefinedEditorElement) currentElement)
								.getcModel().getUnmodifableElementsOnEditor(),
						false);
				break;
			}
		}
	}

	/**
	 * Diese Methode benachrichtigt den zugehoerigen Observer, wenn Aenderungen
	 * stattgefunden haben.
	 */
	public void updateAfterCalc() {
		this.setChanged();
		this.notifyObservers(new SimulationMessage(
				SimulationMessage.SimulationMessageType.UPDATE_AFTER_CALC));
	}

	/**
	 * Methode die eine Map mit den LogicSimulationElements und den zugehoerigen
	 * LogicEditorElements zurueckgibt. Diese Map beinhaltet auch alle Elemente
	 * von selbstdefinierten Bauteilen.
	 * 
	 * @return Map<LogicEditorElement, LogicSimulationElement>
	 */
	public Map<LogicEditorElement, LogicSimulationElement> getSimulationElements() {
		return Collections.unmodifiableMap(sElements);
	}

	/**
	 * Diese Methode gibt alle logischen Elemente als <code>Map</code> vom Typ
	 * <code>LogicSimulationElement</code> zurueck.
	 * 
	 * @return <code>Map</code> - alle logischen Elemente der Schaltung
	 */
	public Map<String, LogicEditorElement> getLogicElements() {
		return this.eModel.getCircuitModel().getUnmodifableElementsOnEditor();
	}

	/**
	 * Diese Methode gibt alle Simulationselemente zurueck, genauso wie sie in
	 * der graphischen Oberflaeche angezeigt werden.
	 * 
	 * @return <code>Map</code> - unmodifiableMap, welche alle zu zeichnenden
	 *         Elemente enthaelt
	 */
	public Map<String, LogicSimulationElement> getUpperSimulationElements() {
		return Collections.unmodifiableMap(this.sElementsToPaint);
	}

	/**
	 * Gibt den <code>ClockHandler</code> zurueck.
	 * 
	 * @return <code>ClockHandler</code>
	 */
	public ClockHandler getCHandler() {
		return cHandler;
	}

	/**
	 * Gibt den Namen des <code>CircuitModel</code> zurueck.
	 * 
	 * @return Namen des <code>CircuitModel</code>
	 */
	public String getCircuitName() {
		return this.eModel.getCircuitModel().getName();
	}

	/**
	 * Gibt den uebergebenen logischen Wert an alle uebergebenen
	 * <code>Input</code>-Objekte weiter und fuegt die betroffenen Bauteile in
	 * die Liste der zu berechnenden Bauteile ein.
	 * 
	 * @param inputs
	 *            <code>List</code> - zu setzende <code>Input</code>-Objekte
	 * @param value
	 *            <code>LogicValue</code> - zu setzender logischer Wert
	 */
	public void propagateValue(final List<Input> inputs, final LogicValue value) {

		// Liste der Inputs durchlaufen und fuer jeden den uebergeben Wert
		// setzen
		// alle elemente an denen ein eingangswert gesetzt wurde, werden
		// in die elementsToCalc geaddet, dass sie danach dann berechnet
		// werden
		for (int i = 0; i < inputs.size(); i++) {
			sElements.get(inputs.get(i).getMyElement()).setInputValue(
					inputs.get(i).getName(), value);
			elementsToCalc.add(sElements.get(inputs.get(i).getMyElement()));
		}
	}

	/**
	 * Diese Methode kehrt den eingestellten Wert eines Schalters um
	 * 
	 * @param switchtoChange
	 *            <code>String</code> - Name des Schalters, wessen Wert
	 *            umgekehrt werden soll
	 * 
	 * @return <code>boolean</code> - <code>true</code>, falls der Wert des
	 *         Schalters geaendert wurde, ansonsten <code>false</code>
	 */
	public boolean changeSwitchValue(final String switchtoChange) {
		if (switchtoChange != null) {
			((LogicSwitch) sElementsToPaint.get(switchtoChange)).changeValue();
			if (actState != SIMSTATE.PLAY) {
				this.setChanged();
				this.notifyObservers(new SimulationMessage(
						SimulationMessage.SimulationMessageType.SET_SWITCH,
						sElementsToPaint.get(switchtoChange)));
			} else {

				Thread thread = new Thread() {
					@Override
					public void run() {
						((LogicSwitch) sElementsToPaint.get(switchtoChange))
								.calcLogicValue();
						calcElementsToCalc();
						updateAfterCalc();

						// setzt nach dem ändern die changePerPulse wieder auf 0
						for (LogicSimulationElement sElem : sElements.values()) {
							sElem.resetChangesPerPulse();
						}
					}
				};
				thread.start();
			}

			return true;
		} else {
			return false;
		}
	}

	/**
	 * Diese Methode kehrt den eingestellten Wert eines Taktgebers um. Das ist
	 * nur moeglich wenn die Simulation gestoppt ist.
	 * 
	 * @param clocktoChange
	 *            <code>String</code> - Name des Taktgebers, wessen Wert
	 *            umgekehrt werden soll
	 * 
	 * @return <code>boolean</code> - <code>true</code>, falls der Wert des
	 *         Taktgebers geaendert wurde, ansonsten <code>false</code>
	 */
	public boolean changeClockValue(String clocktoChange) {

		// man kann den wert eines taktgebers nur verändern wenn simulation
		// gestoppt ist
		if (actState != SIMSTATE.STOP) {
			return false;
		}

		if (clocktoChange != null) {
			((LogicCLOCK) sElementsToPaint.get(clocktoChange))
					.changeStartValue();
			this.setChanged();
			this.notifyObservers(new SimulationMessage(
					SimulationMessage.SimulationMessageType.SET_CLOCK,
					sElementsToPaint.get(clocktoChange)));
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Diese Methode setzt die Frequenz eines Taktgebers.
	 * 
	 * @param clockName
	 *            <code>String</code> - Name des Taktgebers, dessen Frequenz
	 *            gesetzt werden soll
	 * @param frequency
	 *            <code>double</code> - die zu setztende Frequenz
	 * 
	 * @return <code>boolean</code> - <code>true</code>, falls die Frequenz
	 *         geaendert wurde, sonst <code>false</code>
	 */
	public boolean setClockFrequency(String clockName, double frequency) {
		if (clockName != null) {
			((LogicCLOCK) sElementsToPaint.get(clockName))
					.setFrequency(frequency);
			setChanged();
			notifyObservers(new SimulationMessage(
					SimulationMessage.SimulationMessageType.CLOCK_FREQUENCY,
					sElementsToPaint.get(clockName)));
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Entweder: Erzeugt und startet einen <code>SimulationThread</code>. <br>
	 * Oder: Falls die Simulation pausiert wurde, wird sie nun fortgesetzt.
	 */
	public void startSimulation() {

		// wenn man eh schon im playmodus ist wird nix gemacht
		if (actState == SIMSTATE.PLAY) {
			return;
		}

		SIMSTATE stateBefore = actState;

		simulationIsVirgin = false;
		actState = SIMSTATE.PLAY;

		// wenn thread schon gestartet wurde und der benutzer auf den play
		// button drueckt war er vorher pausiert
		if (stateBefore == SIMSTATE.PAUSE) {
			Thread thread = new Thread() {
				@Override
				public void run() {
					for (LogicSimulationElement lSE : clocksAndSwitches
							.values()) {
						if (lSE instanceof LogicSwitch) {
							lSE.calcLogicValue();
						}
					}
					updateAfterCalc();
					
					// setzt nach dem ändern die changePerPulse wieder auf 0
					for (LogicSimulationElement sElem : sElements.values()) {
						sElem.resetChangesPerPulse();
					}
					calcElementsToCalc();
					SimulationModel.this.setChanged();
					SimulationModel.this
							.notifyObservers(new SimulationMessage(
									SimulationMessage.SimulationMessageType.FIRST_CALC_COMPLETE));
				}
			};
			thread.start();

			// setzt das pauseflag auf false und weckt den wartenden simulations
			// thread auf
			cHandler.unpauseClocks();
			synchronized (this) {
				this.notifyAll();
			}
			this.setChanged();
			this.notifyObservers(new SimulationMessage(
					SimulationMessage.SimulationMessageType.RESUME_SIMULATION));
		} else {

			// ansonsten ganz normaler start
			cHandler.setIsInterrupted(false);
			simThread = new SimulationThread();
			simThread.start();
			this.setChanged();
			this.notifyObservers(new SimulationMessage(
					SimulationMessage.SimulationMessageType.START_SIMULATION));
		}
	}

	/**
	 * Pausiert die Simulation, falls <code>LogicCLOCK</code>-Objekte
	 * existieren.
	 */
	public void pauseSimulation() {

		if (actState == SIMSTATE.PAUSE || actState == SIMSTATE.STOP) {
			return;
		}

		actState = SIMSTATE.PAUSE;
		boolean hasClock = false;

		// ueberpruefen ob Taktgeber in der Schaltung vorhanden sind
		for (LogicSimulationElement sEl : clocksAndSwitches.values()) {
			if (sEl instanceof LogicCLOCK) {
				hasClock = true;
			}
		}

		// wenn Taktgeber vorhanden sind wird der Grundtaktgeber pausiert
		if (hasClock) {
			cHandler.pauseClocks();
		}
		
		this.setChanged();
		this.notifyObservers(new SimulationMessage(
				SimulationMessage.SimulationMessageType.PAUSE_SIMULATION,
				Collections.unmodifiableMap(this.sElementsToPaint)));
	}

	/**
	 * Stoppt die Simulation.
	 */
	public void stopSimulation() {

		if (actState == SIMSTATE.STOP) {
			return;
		}

		simulationIsVirgin = true;
		actState = SIMSTATE.STOP;
		cHandler.setIsInterrupted(true);
		cHandler.pauseClocks();

		// zuruecksetzen der Belegungen der Bauteile auf UNDEFINED
		for (LogicSimulationElement sEl : sElements.values()) {
			sEl.refactor();
		}
		this.setChanged();
		this.notifyObservers(new SimulationMessage(
				SimulationMessage.SimulationMessageType.STOP_SIMULATION));
		simThread = null;
	}

	/**
	 * Unterbricht das berechnen der Wahrheitstabelle
	 */
	public void stopCreatingLogicTable() {
		interruptedTruthTableCalculation = true;
	}

	/**
	 * Diese Methode generiert zeilenweise eine Wahrheitstabelle. Dabei gilt
	 * fuer die Kopfzeile folgende Ordnung: zuerst kommen alle Setter,
	 * anschliessend alle Getter. In den darunter liegenden Zeilen befinden sich
	 * dann alle Kombinationen der logischen Werte bzw. Ergebnisse, so wie es
	 * fuer eine Wahrheitstabelle ueblich is.
	 * 
	 * @return <code>LogicValue[][]</code> - die Wahrheitstabelle die an Hand
	 *         des SimualtionModels erzeugt wird
	 */
	public void createLogicTable() {
		interruptedTruthTableCalculation = false;
		final int MAX_SETTERS = 20;
		final int numberOfSetters = this.clocksAndSwitches.size();
		int numberOfGetters = this.lamps.size();
		final ArrayList<LogicSimulationElement> setter;
		final ArrayList<LogicSimulationElement> getter;

		// speichern der startbelegungen der schalter und clocks
		final Map<String, LogicValue> startValues = new HashMap<String, LogicValue>();

		for (LogicSimulationElement switchOrClock : clocksAndSwitches.values()) {
			startValues.put(switchOrClock.getLogicEditorElement().getName(),
					switchOrClock.getLogicValueOfInput(Input.STD_A));
		}

		// falls die Anzahl der Setter kleiner gleich dem gesetzten Maximum ist
		// wird eine Wahrheitstabell erzeugt, ansonsten bekommt der Observer
		// eine Nachricht, dass keine Wahrheitstabelle erzeugt werden kann
		if (numberOfSetters <= MAX_SETTERS) {

			// zuerst werden aus den vorhandenen Maps ArrayListen erzeugt
			setter = new ArrayList<LogicSimulationElement>(
					this.clocksAndSwitches.values());
			getter = new ArrayList<LogicSimulationElement>(this.lamps.values());
			final int tTableHeight = (int) Math.pow(2, numberOfSetters);
			int tTableWidth = numberOfSetters + numberOfGetters;
			final LogicValue[][] tTable = new LogicValue[tTableHeight][tTableWidth];

			// hier werden die logischen Kombinationen Zeilenweise erzeugt,
			// dabei wird einfach binär hochgezählt, wobei das linkeste Bit das
			// niederwertigste ist, also die Binärzahl ist spiegelverkehrt und
			// es werden dann auch immer gleich die zugehoerigen Werte der
			// Getter ermittelt

			// hier wird die berechnung in threads aufgeteilt
			// momentan wird nur 1 thread eingesetzt, weil sonst der
			// gemeinsame zugriff, bzw das lesen auf der tabelle
			// problematisch ist
			final int numberOfThreads = Math.max(1,
					Math.min(1, tTable.length / 10000));
			final Thread[] threads = new Thread[numberOfThreads];
			for (int i = 0; i < numberOfThreads; i++) {
				final int j = i;
				threads[j] = new Thread() {

					private void restoreStartValues() {
						// BLOCK ZUM WIEDERHERSTELLEN DER AUSGANGSBELEGUNG
						// ***********************************************
						for (Map.Entry<String, LogicValue> m : startValues
								.entrySet()) {
							clocksAndSwitches.get(m.getKey()).setInputValue(
									Input.STD_A, m.getValue());
						}
						// wenn die simulation noch nicht gestartet
						// wurde
						// bzw zuvor auf stop gedrueckt wurde muessen
						// die leitungen nach der simulation wieder auf
						// undefined gesetzt werden
						if (!simulationIsVirgin) {
							calcSwitchesAndClocks();
						} else {
							for (LogicSimulationElement lSE : sElements
									.values()) {
								lSE.refactor();
							}
						}
						// ****************************
						// ENDE BLOCK
					}

					@Override
					public void run() {
						if (j == 0) {

							// ist die Berechnung der Wahrheitstabelle beendet
							// wird der Observer
							// benachrichtigt und die Wahrheitstabelle zurueck
							// gegeben
							SimulationModel.this.setChanged();
							SimulationModel.this
									.notifyObservers(new SimulationMessage(
											SimulationMessage.SimulationMessageType.START_TRUTH_TABLE,
											tTable, null, null));
						}

						LogicValue currentValue;
						int upTo = 0;
						if (j == numberOfThreads - 1) {
							upTo = tTable.length;
						} else {
							upTo = j * (tTable.length / numberOfThreads)
									+ (tTable.length / numberOfThreads);
						}
						for (int h = j * (tTable.length / numberOfThreads); h < upTo; ++h) {
							int number = h;
							for (int w = 0; w < numberOfSetters; ++w) {
								if (number % 2 == 1) {
									currentValue = LogicValue.L1;
								} else {
									currentValue = LogicValue.L0;
								}
								tTable[h][w] = currentValue;
								number = number / 2;
							}

							// sobald eine Zeile generiert wurde, werden sofort
							// die
							// zugehoerigen Werte berechnet

							// es wird mit dem Setzen der logischen Werte der
							// Setter
							// begonnen
							int c = 0;
							for (LogicSimulationElement sE : setter) {
								sE.setInputValue(Input.STD_A, tTable[h][c]);
								++c;
							}

							// danach wird eine Berechnung durchgefuehrt
							SimulationModel.this.calcSwitchesAndClocks();

							// abschließend werden die ermitteltn logischen
							// Werte
							// gespeichert
							int y = SimulationModel.this.clocksAndSwitches
									.size();
							for (LogicSimulationElement sE : getter) {
								tTable[h][y] = sE.getLogicValue(Output.STD_O);
								++y;
							}

							if (h % 10000 == 0) {
								SimulationModel.this.setChanged();
								SimulationModel.this
										.notifyObservers(new SimulationMessage(
												SimulationMessage.SimulationMessageType.STATUS_TRUTH_TABLE,
												tTable, null, null));
							}

							if (interruptedTruthTableCalculation) {
								calcSwitchesAndClocks();
								SimulationModel.this.setChanged();
								SimulationModel.this
										.notifyObservers(new SimulationMessage(
												SimulationMessage.SimulationMessageType.END_TRUTH_TABLE,
												null, setter, getter));

								// stellt die ausgangsbelegung wieder her
								restoreStartValues();

								return;
							}
						}

						this.setName("finished");
						for (int k = 0; k < threads.length; k++) {
							if (!threads[k].getName().equals("finished")) {
								break;
							} else if (k == threads.length - 1) {
								SimulationModel.this.setChanged();
								SimulationModel.this
										.notifyObservers(new SimulationMessage(
												SimulationMessage.SimulationMessageType.END_TRUTH_TABLE,
												tTable, setter, getter));

								// stellt die ausgangsbelegung wieder her
								restoreStartValues();
							}
						}
					}
				};
				threads[j].start();
			}
		} else {
			this.setChanged();
			this.notifyObservers(new SimulationMessage(
					SimulationMessage.SimulationMessageType.TOO_MUCH_SETTERS));
		}
	}

	/**
	 * Berechnet die Bauteile die in der Liste der zu berechnenden Bauteile
	 * steht. Beim Start der Simulation wird diese Methode aufgerufen. Beim
	 * Berechnen der Bauteile gibt es wieder nachfolgende Bauteile die dann
	 * berechnet werden muessen. Diese werden dann in die elementsToCalc Liste
	 * eingefuegt. Diese Methode endet erst, wenn keine Elemente mehr in der
	 * Liste stehen. Bei jedem Durchlauf der while-Schleife wird die Liste der
	 * zu berechnenden Bauteile erst kopiert, und dann geleert. Beim durchlaufen
	 * der kopierten Liste werden die neu zu berechnenden Bauteile dann in die
	 * vorher geleerte Liste eingefuegt.
	 */
	public synchronized void calcElementsToCalc() {
		while (!elementsToCalc.isEmpty()) {

			// AN DER STELLE KOENNTE EIN SLEEP EINGEFUEGT WERDEN UM
			// DAS PROGRAMM EVENTUELL MIT EINEM DELAY ZUERWEITERN

			List<LogicSimulationElement> toCalc = new ArrayList<LogicSimulationElement>(
					elementsToCalc);
			elementsToCalc.clear();
			for (LogicSimulationElement lSE : toCalc) {
				lSE.calcLogicValue();
			}
		}
	}

	/**
	 * Private Hilfsmethode zum Berechnen der logischen Werte an den Ausgaengen
	 * aller Schalter und Taktgeber. Bevor diese Methode ausgefuehrt wirde
	 * werden alle Belegungen, ausser die der Setter auf Undefined gesetzt.
	 */
	private void calcSwitchesAndClocks() {
		// zuruecksetzen der Belegungen der Bauteile auf UNDEFINED
		for (LogicSimulationElement sEl : sElements.values()) {
			if (!(sEl instanceof LogicCLOCK)) {
				sEl.refactor();
			} else {
				sEl.resetChangesPerPulse();
			}
		}
		for (LogicSimulationElement sEl : clocksAndSwitches.values()) {
			sEl.calcLogicValue();
		}
		this.calcElementsToCalc();
	}

	/**
	 * Diese Methode wird aufgerufen, wenn in einem Element festgestellt wurde,
	 * dass die Schaltung zirkuliert
	 * 
	 * @param lEE
	 *            <code>LogicEditorElement</code> - Elements bei dem als erstes
	 *            der Hinweis für eine Zirkulation auftritt
	 */
	public void oszillationFound(LogicEditorElement lEE) {
		LogicSimulationElement lSE = this.sElementsToPaint.get(lEE.getName());
		setChanged();
		this.notifyObservers(new SimulationMessage(
				SimulationMessage.SimulationMessageType.OSZILLATION_FOUND, lSE));
	}

	/**
	 * <b>Erbt:</b> <code>Thread</code> <br>
	 * Fuehrt die Berechnungen waehrend der Simulation als eigener Thread durch.
	 * Ausserdem kontrolliert diese innere Klasse die Einhaltung der
	 * Taktfrequenz.
	 * 
	 * @author Team Locsi
	 * 
	 */
	private class SimulationThread extends Thread {

		/**
		 * Ruft die private Hilfsmethode <code>addClocksToClockHandler()</code>
		 * auf. Danach wird ein Zeitstempel gesetzt. Danach wird die private
		 * Hilfsmethode <code>calcSwitchesAndClocks</code> aufgerufen.
		 * Anschliessend wird die Dauer der Berechnung der
		 * <code>calcSwitchesAndClocks()</code>-Methode berechnet. Daraufhin
		 * wird die <code>startClocks()</code>-Methode des
		 * <code>ClockHandler</code> aufgerufen.
		 */
		@Override
		public void run() {
			addClocksToClockHandler();
			long start = System.currentTimeMillis();
			calcSwitchesAndClocks();
			updateAfterCalc();
			long calcDuration = System.currentTimeMillis() - start;
			SimulationModel.this.setChanged();
			SimulationModel.this
					.notifyObservers(new SimulationMessage(
							SimulationMessage.SimulationMessageType.FIRST_CALC_COMPLETE));
			boolean hasClock = false;

			for (LogicSimulationElement sEl : clocksAndSwitches.values()) {
				if (sEl instanceof LogicCLOCK) {
					hasClock = true;
					break;
				}
			}

			if (hasClock) {

				// startet die clocks mit der oben gemessenen firstCalcduration
				cHandler.startClocks(calcDuration);
			}
		}

		/**
		 * Private Hilfsmethode zum Anmelden aller Taktgeber beim
		 * <code>ClockHandler</code> als <code>Observer</code>.
		 */
		private void addClocksToClockHandler() {

			// vorher observers removen und neu adden
			// sElements durchgehen und LogicCLOCKs anmelden am ClockHandler
			cHandler.resetHandler();
			cHandler.deleteObservers();
			for (LogicSimulationElement sEl : clocksAndSwitches.values()) {
				if (sEl instanceof LogicCLOCK) {
					cHandler.addObserver(((LogicCLOCK) sEl));
				}
			}
		}

		/**
		 * Private Hilfsmethode zum Berechnen der logischen Werte an den
		 * Ausgaengen aller Schalter und Taktgeber.
		 */
		private void calcSwitchesAndClocks() {
			for (LogicSimulationElement sEl : clocksAndSwitches.values()) {
				sEl.calcLogicValue();
			}
			calcElementsToCalc();
		}
	}
}